#ifndef ThingsBoard_h
#define ThingsBoard_h

// Local includes.
#include "Constants.h"
#include "IAPI_Implementation.h"
#include "IMQTT_Client.h"
#include "DefaultLogger.h"
#include "Telemetry.h"

// Library includes.
#if THINGSBOARD_ENABLE_STREAM_UTILS
#include <StreamUtils.h>
#endif // THINGSBOARD_ENABLE_STREAM_UTILS


uint16_t constexpr DEFAULT_MQTT_PORT = 1883U;
char constexpr PROV_ACCESS_TOKEN[] = "provision";
// Log messages.
char constexpr UNABLE_TO_DE_SERIALIZE_JSON[] = "Unable to de-serialize received json data with error (DeserializationError::%s)";
char constexpr INVALID_BUFFER_SIZE[] = "Send buffer size (%u) to small for the given payloads size (%u), increase with setBufferSize accordingly or install the StreamUtils library";
char constexpr UNABLE_TO_ALLOCATE_BUFFER[] = "Allocating memory for the internal MQTT buffer failed";
char constexpr MAX_ENDPOINTS_AMOUNT_TEMPLATE_NAME[] = "MaxEndpointsAmount";
#if THINGSBOARD_ENABLE_DYNAMIC
char constexpr MAXIMUM_RESPONSE_EXCEEDED[] = "Prevented allocation on the heap (%u) for JsonDocument. Discarding message that is bigger than maximum response size (%u)";
char constexpr HEAP_ALLOCATION_FAILED[] = "Failed allocating required size (%u) for JsonDocument. Ensure there is enough heap memory left";
#endif // THINGSBOARD_ENABLE_DYNAMIC
#if THINGSBOARD_ENABLE_DEBUG
char constexpr RECEIVE_MESSAGE[] = "Received (%u) bytes of data from server over topic (%s)";
char constexpr ALLOCATING_JSON[] = "Allocated internal JsonDocument for MQTT server response with size (%u)";
char constexpr SEND_MESSAGE[] = "Sending data to server over topic (%s) with data (%s)";
char constexpr SEND_SERIALIZED[] = "Hidden, because json data is bigger than buffer, therefore showing in console is skipped";
#endif // THINGSBOARD_ENABLE_DEBUG
// Claim topics.
char constexpr CLAIM_TOPIC[] = "v1/devices/me/claim";
// Claim data keys.
char constexpr SECRET_KEY[] = "secretKey";
char constexpr DURATION_KEY[] = "durationMs";


#if THINGSBOARD_ENABLE_DYNAMIC
/// @brief Wrapper around any arbitrary MQTT Client implementing the IMQTT_Client interface, to allow connecting and sending / retrieving data from ThingsBoard over the MQTT or MQTT with TLS/SSL protocol.
/// BufferSize of the underlying data buffer can be changed during the runtime and the maximum amount of data points that can ever be sent or received are automatically deduced at runtime.
/// Additionally, there are internal vectors that hold all subscriptions and requests and dynamically allocate memory on the heap, depending on how much space we currently require.
/// Furthermore, there are internal vectors in the Shared_Attribute_Callback and the Attribute_Request_Callback, which hold the amount of keys we want to request or subscribe to updates too.
/// Dynamically increasing the internal size, allows to adjust how much space we require depending on the amount of subscribed or requested keys.
/// If this feature of automatic deduction, is not needed, or not wanted because it allocates memory on the heap, then the values can be set once as template arguements.
/// Simply set THINGSBOARD_ENABLE_DYNAMIC to 0, before including ThingsBoard.h
/// @tparam Logger Implementation that should be used to print error messages generated by internal processes and additional debugging messages if THINGSBOARD_ENABLE_DEBUG is set, default = DefaultLogger
template <typename Logger = DefaultLogger>
#else
/// @brief Wrapper around any arbitrary MQTT Client implementing the IMQTT_Client interface, to allow connecting and sending / retrieving data from ThingsBoard over the MQTT or MQTT with TLS/SSL protocol.
/// BufferSize of the underlying data buffer can be changed during the runtime and the maximum amount of data points that can ever be can be set once as template argument.
/// Additionally, there are internal arrays that hold all subscriptions and requests and statically allocate memory on the stack, which can also be set once as a template argument.
/// Furthermore, there are the maximum amount of values for the internal arrays of the Shared_Attribute_Callback and the Attribute_Request_Callback, which hold the amount of keys we want to request or subscribe to updates too
/// Setting a fixed size, allows to allocate the variables in the container on the stack, which can also be set once as a template argument.
/// Changing is only possible if a new instance of this class is created. If these values should be automatically deduced at runtime instead then, and then dynamically allocated on the heap,
/// simply set THINGSBOARD_ENABLE_DYNAMIC to 1, before including ThingsBoard.h
/// @tparam MaxResponse Maximum amount of key value pair that will ever be received by ThingsBoard in one call, default = Default_Response_Amount (8)
/// @tparam MaxEndpointsAmount Maximum amount of subscribed API endpoints, Default_Endpoints_Amount is used as the default value because it is big enough to hold one instance of every possible API Implementation, default = Default_Endpoints_Amount (7)
/// @tparam Logger Implementation that should be used to print error messages generated by internal processes and additional debugging messages if THINGSBOARD_ENABLE_DEBUG is set, default = DefaultLogger
template<size_t MaxResponse = Default_Response_Amount, size_t MaxEndpointsAmount = Default_Endpoints_Amount, typename Logger = DefaultLogger>
#endif // THINGSBOARD_ENABLE_DYNAMIC
class ThingsBoardSized {
  public:
    /// @brief Constructs a ThingsBoardSized instance with the given network client that should be used to establish the connection to ThingsBoard.
    /// Directly forwards the last given arguments to the overloaded Array or Vector (THINGSBOARD_ENABLE_DYNAMIC) constructor,
    /// meaning all combinatons of arguments that would initalize the Array or Vector (THINGSBOARD_ENABLE_DYNAMIC) can be used to call this constructor.
    /// The possibilites mainly consist out of the default constructor which creates an empty internal buffer with no data
    /// or out of the range constructor where we can pass an interator the start of another data container
    /// and to the end of the data container (last element + 1) and then every element between those iteratos will be copied, in the same order as in the original data container
    /// @tparam ...Args Holds the multiple arguments that will simply be forwarded to the Array or Vector (THINGSBOARD_ENABLE_DYNAMIC) constructor and therefore allow to use every overloaded vector constructor without having to implement them
    /// @param client MQTT Client implementation that should be used to establish the connection to ThingsBoard
    /// @param receive_buffer_size Maximum amount of data that can be received by this device at once, if bigger packets are received they are discarded and the update lost instead, default = Default_Payload_Size (64)
    /// @param send_buffer_size Maximum amount of data that can be sent from this device at once. If we attempt to send data that is bigger, it will not be sent instead.
    /// Alternatively setting THINGSBOARD_ENABLE_STREAM_UTILS to 1 allows to send arbitrary size payloads if that is done the internal buffer of the MQTT Client implementation
    /// can be theoretically set the value as big as the buffering_size passed to the constructor + enough memory to hold the topic and MQTT Header ~= 20 bytes.
    /// This will mean though that all messages are sent over the StreamUtils library as long as they are bigger than the internal buffer,
    /// which needs more time than sending a message directly but has the advantage of requiring less memory.
    /// So if the available heap memory is a problem on the board it might be useful to enable the THINGSBOARD_ENABLE_STREAM_UTILS option.
    /// This can be done by simply using Arduino as the framework and installing the StreamUtils (https://github.com/bblanchon/ArduinoStreamUtils) library, default = Default_Payload_Size (64)
    /// @param max_stack_size Maximum amount of bytes we want to allocate on the stack, default = Default_Max_Stack_Size (1024)
    /// @param ...args Arguments that will be forwarded into the overloaded Array or Vector (THINGSBOARD_ENABLE_DYNAMIC) constructor
    template<typename... Args>
#if THINGSBOARD_ENABLE_DYNAMIC
#if THINGSBOARD_ENABLE_STREAM_UTILS
    /// @param buffering_size Amount of bytes allocated to speed up serialization, default = Default_Buffering_Size (64)
    /// @param max_response_size Maximum amount of bytes allocated for the interal JsonDocument structure that holds the received payload.
    /// Size is calculated automatically from certain characters in the received payload (',', '{', '[') but if we receive a malicious payload that contains these symbols in a string {"example":",,,,,,..."}.
    /// It is possible to cause huge allocations, nut because the memory only lives for as long as the subscribed callback methods it should not be a problem,
    /// especially because attempting to allocate too much memory, will cause the allocation to fail, which is checked. But if the failure of that heap allocation is subscribed for example with the heap_caps_register_failed_alloc_callback method on the ESP32,
    /// then that subscribed callback will be called and could theoretically restart the device. To circumvent that we can simply set the size of this variable to a value that should never be exceeded by a non malicious json payload, received by attribute requests, shared attribute updates, server-side or client-side rpc.
    /// If this safety feature is not required, because the heap allocation failure callback is not subscribed, then the value of the variable can simply be kept as 0, which means we will not check the received payload for its size before the allocation happens, default = Default_Max_Response_Size (0)
    ThingsBoardSized(IMQTT_Client & client, uint16_t receive_buffer_size = Default_Payload_Size, uint16_t send_buffer_size = Default_Payload_Size, size_t const & max_stack_size = Default_Max_Stack_Size, size_t const & buffering_size = Default_Buffering_Size, size_t const & max_response_size = Default_Max_Response_Size, Args const &... args)
#else
    /// @param max_response_size Maximum amount of bytes allocated for the interal JsonDocument structure that holds the received payload.
    /// Size is calculated automatically from certain characters in the received payload (',', '{', '[') but if we receive a malicious payload that contains these symbols in a string {"example":",,,,,,..."}.
    /// It is possible to cause huge allocations, nut because the memory only lives for as long as the subscribed callback methods it should not be a problem,
    /// especially because attempting to allocate too much memory, will cause the allocation to fail, which is checked. But if the failure of that heap allocation is subscribed for example with the heap_caps_register_failed_alloc_callback method on the ESP32,
    /// then that subscribed callback will be called and could theoretically restart the device. To circumvent that we can simply set the size of this variable to a value that should never be exceeded by a non malicious json payload, received by attribute requests, shared attribute updates, server-side or client-side rpc.
    /// If this safety feature is not required, because the heap allocation failure callback is not subscribed, then the value of the variable can simply be kept as 0, which means we will not check the received payload for its size before the allocation happens, default = Default_Max_Response_Size (0)
    ThingsBoardSized(IMQTT_Client & client, uint16_t receive_buffer_size = Default_Payload_Size, uint16_t send_buffer_size = Default_Payload_Size, size_t const & max_stack_size = Default_Max_Stack_Size, size_t const & max_response_size = Default_Max_Response_Size, Args const &... args)
#endif // THINGSBOARD_ENABLE_STREAM_UTILS
#else
#if THINGSBOARD_ENABLE_STREAM_UTILS
    /// @param buffering_size Amount of bytes allocated to speed up serialization, default = Default_Buffering_Size (64)
    ThingsBoardSized(IMQTT_Client & client, uint16_t receive_buffer_size = Default_Payload_Size, uint16_t send_buffer_size = Default_Payload_Size, size_t const & max_stack_size = Default_Max_Stack_Size, size_t const & buffering_size = Default_Buffering_Size, Args const &... args)
#else
    ThingsBoardSized(IMQTT_Client & client, uint16_t receive_buffer_size = Default_Payload_Size, uint16_t send_buffer_size = Default_Payload_Size, size_t const & max_stack_size = Default_Max_Stack_Size, Args const &... args)
#endif // THINGSBOARD_ENABLE_STREAM_UTILS
#endif // THINGSBOARD_ENABLE_DYNAMIC
      : m_client(client)
      , m_max_stack(max_stack_size)
#if THINGSBOARD_ENABLE_STREAM_UTILS
      , m_buffering_size(buffering_size)
#endif // THINGSBOARD_ENABLE_STREAM_UTILS
#if THINGSBOARD_ENABLE_DYNAMIC
       , m_max_response_size(max_response_size)
#endif // THINGSBOARD_ENABLE_DYNAMIC
      , m_api_implementations(args...)
    {
        for (auto & api : m_api_implementations) {
            if (api == nullptr) {
                continue;
            }
#if THINGSBOARD_ENABLE_STL
            api->Set_Client_Callbacks(std::bind(&ThingsBoardSized::Subscribe_API_Implementation, this, std::placeholders::_1), std::bind(&ThingsBoardSized::Send_Json, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), std::bind(&ThingsBoardSized::Send_Json_String, this, std::placeholders::_1, std::placeholders::_2), std::bind(&ThingsBoardSized::clientSubscribe, this, std::placeholders::_1), std::bind(&ThingsBoardSized::clientUnsubscribe, this, std::placeholders::_1), std::bind(&ThingsBoardSized::getClientReceiveBufferSize, this), std::bind(&ThingsBoardSized::getClientSendBufferSize, this), std::bind(&ThingsBoardSized::setBufferSize, this, std::placeholders::_1, std::placeholders::_2), std::bind(&ThingsBoardSized::getRequestID, this));
#else
            api->Set_Client_Callbacks(ThingsBoardSized::staticSubscribeImplementation, ThingsBoardSized::staticSendJson, ThingsBoardSized::staticSendJsonString, ThingsBoardSized::staticClientSubscribe, ThingsBoardSized::staticClientUnsubscribe, ThingsBoardSized::staticGetClientReceiveBufferSize, ThingsBoardSized::staticGetClientSendBufferSize, ThingsBoardSized::staticSetBufferSize, ThingsBoardSized::staticGetRequestID);
#endif // THINGSBOARD_ENABLE_STL
            api->Initialize();
        }
        (void)setBufferSize(receive_buffer_size, send_buffer_size);
        // Initialize callback.
#if THINGSBOARD_ENABLE_STL
        m_client.set_data_callback(std::bind(&ThingsBoardSized::onMQTTMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
        m_client.set_connect_callback(std::bind(&ThingsBoardSized::Resubscribe_Topics, this));
#else
        m_client.set_data_callback(ThingsBoardSized::onStaticMQTTMessage);
        m_client.set_connect_callback(ThingsBoardSized::staticMQTTConnect);
        m_subscribedInstance = this;
#endif // THINGSBOARD_ENABLE_STL
    }

    /// @brief Gets the currently connected MQTT Client implementation as a reference.
    /// Allows for calling method directly on the client itself, not advised in normal use cases,
    /// as it might cause problems if the library expects the client to be sending / receiving data
    /// but it can not do that anymore, because it has been disconnected or certain settings were changed
    /// @return Reference to the underlying MQTT Client implementation connected to ThingsBoard
    IMQTT_Client & getClient() {
        return m_client;
    }

    /// @brief Sets the maximum amount of bytes that we want to allocate on the stack, before the memory is allocated on the heap instead
    /// @param max_stack_size Maximum amount of bytes we want to allocate on the stack
    void setMaximumStackSize(size_t const & max_stack_size) {
        m_max_stack = max_stack_size;
    }

#if THINGSBOARD_ENABLE_STREAM_UTILS
    /// @brief Sets the amount of bytes that can be allocated to speed up fall back serialization with the StreamUtils class
    /// See https://github.com/bblanchon/ArduinoStreamUtils for more information on the underlying class used
    /// @param buffering_size Amount of bytes allocated to speed up serialization
    void setBufferingSize(size_t const & buffering_size) {
        m_buffering_size = buffering_size;
    }
#endif // THINGSBOARD_ENABLE_STREAM_UTILS

#if THINGSBOARD_ENABLE_DYNAMIC
    /// @brief Sets the maximum amount of bytes allocated for internal JsonDocument holding received payload from server responses by attribute requests, shared attribute updates, server-side or client-side rpc
    /// @param max_response_size Maximum amount of bytes allocated for the interal JsonDocument structure that holds the received payload.
    /// Size is calculated automatically from certain characters in the received payload (',', '{', '[') but if we receive a malicious payload that contains these symbols in a string {"example":",,,,,,..."}.
    /// It is possible to cause huge allocations, nut because the memory only lives for as long as the subscribed callback methods it should not be a problem,
    /// especially because attempting to allocate too much memory, will cause the allocation to fail, which is checked. But if the failure of that heap allocation is subscribed for example with the heap_caps_register_failed_alloc_callback method on the ESP32,
    /// then that subscribed callback will be called and could theoretically restart the device. To circumvent that we can simply set the size of this variable to a value that should never be exceeded by a non malicious json payload, received by attribute requests, shared attribute updates, server-side or client-side rpc.
    /// If this safety feature is not required, because the heap allocation failure callback is not subscribed, then the value of the variable can simply be kept as 0, which means we will not check the received payload for its size before the allocation happens, default = Default_Max_Response_Size (0)
    void setMaxResponseSize(size_t const & max_response_size) {
        m_max_response_size = max_response_size;
    }
#endif // THINGSBOARD_ENABLE_DYNAMIC

    /// @brief Sets the size of the buffer for the underlying network client that will be used to establish the connection to ThingsBoard.
    /// The internal values can be changed later again, at any time with the setBufferSize() method. Is split into two arguments, because it allows seperating the buffer that received data from the one that sends data.
    /// This makes it possible to optimize the memory used and to handle received data without copying it, while sending data in between.
    /// Meaning it is possible to read the values in callback functions even after you send more data from this device. 
    /// @param receive_buffer_size Maximum amount of data that can be received by this device at once, if bigger packets are received they are discarded and the update lost instead
    /// @param send_buffer_size Maximum amount of data that can be sent from this device at once. If we attempt to send data that is bigger, it will not be sent instead.
    /// Alternatively setting THINGSBOARD_ENABLE_STREAM_UTILS to 1 allows to send arbitrary size payloads if that is done the internal buffer of the MQTT Client implementation
    /// can be theoretically set the value as big as the buffering_size passed to the constructor + enough memory to hold the topic and MQTT Header ~= 20 bytes.
    /// This will mean though that all messages are sent over the StreamUtils library as long as they are bigger than the internal buffer,
    /// which needs more time than sending a message directly but has the advantage of requiring less memory.
    /// So if the available heap memory is a problem on the board it might be useful to enable the THINGSBOARD_ENABLE_STREAM_UTILS option.
    /// This can be done by simply using Arduino as the framework and installing the StreamUtils (https://github.com/bblanchon/ArduinoStreamUtils) library
    /// @return Whether allocating the needed memory for the given buffer sizes was successful or not
    bool setBufferSize(uint16_t receive_buffer_size, uint16_t send_buffer_size) {
        bool const result = m_client.set_buffer_size(receive_buffer_size, send_buffer_size);
        if (!result) {
            Logger::printfln(UNABLE_TO_ALLOCATE_BUFFER);
        }
        return result;
    }

    /// @brief Clears all currently subscribed callbacks and unsubscribed from all
    /// currently subscribed MQTT topics, any response that will stil be received is discarded
    /// and any ongoing firmware update is aborted and will not be finished.
    /// Was previously done automatically in the connect() method, but is not done anymore,
    /// because connect() method now reconencts to all previously subscribed MQTT topics instead,
    /// therefore there is no need anymore to discard all previously subscribed callbacks and letting the user resubscribe
    void Cleanup_Subscriptions() {
        // Results are ignored, because the important part of clearing internal data structures always succeeds
        for (auto & api : m_api_implementations) {
            if (api == nullptr) {
                continue;
            }
            (void)api->Unsubscribe();
        }
    }

    /// @brief Connects to the specified ThingsBoard server over the given port as the given device.
    /// If there are still active server-side RPC or Shared Attribute subscriptions, the aforementioned topics will be resubscribed automatically.
    /// Additionally internal vectors are kept the same so any permanent subscriptions, does not need to be resubscribed by calling the appropriate subscribe methods again.
    /// @param host ThingsBoard server instance we want to connect to
    /// @param access_token Access token that connects this device with a created device on the ThingsBoard server,
    /// can be "provision", if the device creates itself instead. See https://thingsboard.io/docs/user-guide/device-provisioning/?mqttprovisioning=without#provision-device-apis for more information, default = PROV_ACCESS_TOKEN ("provision")
    /// @param port Port that will be used to establish a connection and send / receive data from ThingsBoard over, default = DEFAULT_MQTT_PORT (1883)
    /// @param client_id Client username that can be used to differentiate the user that is connecting the given device to ThingsBoard, recommended to be a unique identifier
    /// so it possible to discern which device is communicating, default = Value of passed access token
    /// @param password Client password that can be used to authenticate the user that is connecting the given device to ThingsBoard, default = nullptr
    /// @return Whether connecting to ThingsBoard was successful or not
    bool connect(char const * host, char const * access_token = PROV_ACCESS_TOKEN, uint16_t port = DEFAULT_MQTT_PORT, char const * client_id = nullptr, char const * password = nullptr) {
        if (host == nullptr) {
            return false;
        }
        m_client.set_server(host, port);
        return connectToHost(access_token, Helper::stringIsNullorEmpty(client_id) ? access_token : client_id, Helper::stringIsNullorEmpty(password) ? nullptr : password);
    }

    /// @brief Disconnects any connection that has been established already
    void disconnect() {
        m_client.disconnect();
    }

    /// @brief Returns our current connection status to the cloud, true meaning we are connected,
    /// false meaning we have been disconnected or have not established a connection yet
    /// @return Whether the underlying MQTT Client is currently connected or not
    bool connected() {
        return m_client.connected();
    }

    /// @brief Receives / sends any outstanding messages from and to the MQTT broker.
    /// Additionally when not being able to use the ESP Timer, it updates the internal timeout timers
    /// @return Whether sending or receiving the oustanding the messages was successful or not
    bool loop() {
#if !THINGSBOARD_USE_ESP_TIMER
        for (auto & api : m_api_implementations) {
            if (api == nullptr) {
                continue;
            }
            api->loop();
        }
#endif // !THINGSBOARD_USE_ESP_TIMER
        return m_client.loop();
    }

    /// @brief Attempts to send key value pairs from custom source over the given topic to the server
    /// @param topic Topic we want to send the data over
    /// @param source JsonDocument containing our json key value pairs we want to send,
    /// is checked before usage for any possible occuring internal errors. See https://arduinojson.org/v6/api/jsondocument/ for more information
    /// @param json_size Size of the data inside the source
    /// @return Whether sending the data was successful or not
    bool Send_Json(char const * topic, JsonDocument const & source, size_t const & json_size) {
        // Check if allocating needed memory failed when trying to create the JsonDocument,
        // if it did the isNull() method will return true. See https://arduinojson.org/v6/api/jsonvariant/isnull/ for more information
        if (source.isNull()) {
            Logger::printfln(UNABLE_TO_ALLOCATE_JSON);
            return false;
        }
        // Check if inserting any of the internal values failed because the JsonDocument was too small,
        // if it did the overflowed() method will return true. See https://arduinojson.org/v6/api/jsondocument/overflowed/ for more information
        if (source.overflowed()) {
            Logger::printfln(JSON_SIZE_TO_SMALL);
            return false;
        }
        bool result = false;

#if THINGSBOARD_ENABLE_STREAM_UTILS
        // Check if the size of the given message would be too big for the actual client,
        // if it is utilize the serialize json work around, so that the internal client buffer can be circumvented
        if (m_client.get_buffer_size() < json_size)  {
#if THINGSBOARD_ENABLE_DEBUG
            Logger::printfln(SEND_MESSAGE, topic, SEND_SERIALIZED);
#endif // THINGSBOARD_ENABLE_DEBUG
            result = Serialize_Json(topic, source, json_size - 1);
        }
        // Check if the remaining stack size of the current task would overflow the stack,
        // if it would allocate the memory on the heap instead to ensure no stack overflow occurs
        else
#endif // THINGSBOARD_ENABLE_STREAM_UTILS
        if (json_size > getMaximumStackSize()) {
            char* json = new char[json_size]();
            if (serializeJson(source, json, json_size) < json_size - 1) {
                Logger::printfln(UNABLE_TO_SERIALIZE_JSON);
            }
            else {
                result = Send_Json_String(topic, json);
            }
            // Ensure to actually delete the memory placed onto the heap, to make sure we do not create a memory leak
            // and set the pointer to null so we do not have a dangling reference.
            delete[] json;
            json = nullptr;
        }
        else {
            char json[json_size] = {};
            if (serializeJson(source, json, json_size) < json_size - 1) {
                Logger::printfln(UNABLE_TO_SERIALIZE_JSON);
                return result;
            }
            result = Send_Json_String(topic, json);
        }

        return result;
    }

    /// @brief Attempts to send custom json string over the given topic to the server
    /// @param topic Topic we want to send the data over
    /// @param json String containing our json key value pairs we want to attempt to send
    /// @return Whether sending the data was successful or not
    bool Send_Json_String(char const * topic, char const * json) {
        if (json == nullptr) {
            return false;
        }

        uint16_t current_send_buffer_size = m_client.get_send_buffer_size();
        size_t const json_size = strlen(json);

        if (current_send_buffer_size < json_size) {
            Logger::printfln(INVALID_BUFFER_SIZE, current_send_buffer_size, json_size);
            return false;
        }

#if THINGSBOARD_ENABLE_DEBUG
        Logger::printfln(SEND_MESSAGE, topic, json);
#endif // THINGSBOARD_ENABLE_DEBUG
        return m_client.publish(topic, reinterpret_cast<uint8_t const *>(json), json_size);
    }

    /// @brief Copies a non-owning pointer to the given API implementation, into the local data container.
    /// Ensure the actual variable is kept alive for as long as the instance of this class
    /// @param api Additional API that we want to be handled
    void Subscribe_API_Implementation(IAPI_Implementation & api) {
#if !THINGSBOARD_ENABLE_DYNAMIC
        if (m_api_implementations.size() + 1 > m_api_implementations.capacity()) {
            Logger::printfln(MAX_SUBSCRIPTIONS_EXCEEDED, MAX_ENDPOINTS_AMOUNT_TEMPLATE_NAME, MaxEndpointsAmount);
            return;
        }
#endif // !THINGSBOARD_ENABLE_DYNAMIC
#if THINGSBOARD_ENABLE_STL
        api.Set_Client_Callbacks(std::bind(&ThingsBoardSized::Subscribe_API_Implementation, this, std::placeholders::_1), std::bind(&ThingsBoardSized::Send_Json, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), std::bind(&ThingsBoardSized::Send_Json_String, this, std::placeholders::_1, std::placeholders::_2), std::bind(&ThingsBoardSized::clientSubscribe, this, std::placeholders::_1), std::bind(&ThingsBoardSized::clientUnsubscribe, this, std::placeholders::_1), std::bind(&ThingsBoardSized::getClientReceiveBufferSize, this), std::bind(&ThingsBoardSized::getClientSendBufferSize, this), std::bind(&ThingsBoardSized::setBufferSize, this, std::placeholders::_1, std::placeholders::_2), std::bind(&ThingsBoardSized::getRequestID, this));
#else
        api.Set_Client_Callbacks(ThingsBoardSized::staticSubscribeImplementation, ThingsBoardSized::staticSendJson, ThingsBoardSized::staticSendJsonString, ThingsBoardSized::staticClientSubscribe, ThingsBoardSized::staticClientUnsubscribe, ThingsBoardSized::staticGetClientReceiveBufferSize, ThingsBoardSized::staticGetClientSendBufferSize, ThingsBoardSized::staticSetBufferSize, ThingsBoardSized::staticGetRequestID);
#endif // THINGSBOARD_ENABLE_STL
        api.Initialize();
        m_api_implementations.push_back(&api);
    }

    /// @brief Copies the non-owning pointers to the given API implementations, into the local data container.
    /// Expects iterators to a container containing API implementations instances.
    /// Ensure the actual memory of the API implementations inside the data container are kept alive for as long as the instance of this class
    /// @tparam InputIterator Class that points to the begin and end iterator
    /// of the given data container, allows for using / passing either std::vector or std::array.
    /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator
    /// @param first Iterator pointing to the first element in the data container
    /// @param last Iterator pointing to the end of the data container (last element + 1)
    template <typename InputIterator>
    void Subscribe_API_Implementations(InputIterator const & first, InputIterator const & last) {
#if !THINGSBOARD_ENABLE_DYNAMIC
        size_t const size = Helper::distance(first, last);
        if (m_api_implementations.size() + size > m_api_implementations.capacity()) {
            Logger::printfln(MAX_SUBSCRIPTIONS_EXCEEDED, MAX_ENDPOINTS_AMOUNT_TEMPLATE_NAME, MaxEndpointsAmount);
            return;
        }
#endif // !THINGSBOARD_ENABLE_DYNAMIC
        for (auto it = first; it != last; ++it) {
            auto & api = *it;
            if (api == nullptr) {
                continue;
            }
#if THINGSBOARD_ENABLE_STL
            api->Set_Client_Callbacks(std::bind(&ThingsBoardSized::Subscribe_API_Implementation, this, std::placeholders::_1), std::bind(&ThingsBoardSized::Send_Json, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), std::bind(&ThingsBoardSized::Send_Json_String, this, std::placeholders::_1, std::placeholders::_2), std::bind(&ThingsBoardSized::clientSubscribe, this, std::placeholders::_1), std::bind(&ThingsBoardSized::clientUnsubscribe, this, std::placeholders::_1), std::bind(&ThingsBoardSized::getClientReceiveBufferSize, this), std::bind(&ThingsBoardSized::getClientSendBufferSize, this), std::bind(&ThingsBoardSized::setBufferSize, this, std::placeholders::_1, std::placeholders::_2), std::bind(&ThingsBoardSized::getRequestID, this));
#else
            api->Set_Client_Callbacks(ThingsBoardSized::staticSubscribeImplementation, ThingsBoardSized::staticSendJson, ThingsBoardSized::staticSendJsonString, ThingsBoardSized::staticClientSubscribe, ThingsBoardSized::staticClientUnsubscribe, ThingsBoardSized::staticGetClientReceiveBufferSize, ThingsBoardSized::staticGetClientSendBufferSize, ThingsBoardSized::staticSetBufferSize, ThingsBoardSized::staticGetRequestID);
#endif // THINGSBOARD_ENABLE_STL
            api->Initialize();
        }
        m_api_implementations.insert(m_api_implementations.end(), first, last);
    }

    //----------------------------------------------------------------------------
    // Claiming API

    /// @brief Sends a claiming request for the given device, allowing any given user on the cloud to assign the device as their own (claim),
    /// as long as they enter the given device name and secret key in the given amount of time.
    /// Optionally a secret key can be passed or be left empty (cloud will allow any user to claim the device for the given amount of time).
    /// See https://thingsboard.io/docs/user-guide/claiming-devices/ for more information
    /// @param secret_key Password the user additionaly to the device name needs to enter to claim it as their own,
    /// pass nullptr or an empty string if the user should be able to claim the device without any password
    /// @param duration_ms Total time in milliseconds the user has to claim their device as their own
    /// @return Whether sending the claiming request was successful or not
    bool Claim_Request(char const * secret_key, size_t const & duration_ms) {
        StaticJsonDocument<JSON_OBJECT_SIZE(2)> request_buffer;

        if (!Helper::stringIsNullorEmpty(secret_key)) {
            request_buffer[SECRET_KEY] = secret_key;
        }
        request_buffer[DURATION_KEY] = duration_ms;
        return Send_Json(CLAIM_TOPIC, request_buffer, Helper::Measure_Json(request_buffer));
    }

    //----------------------------------------------------------------------------
    // Telemetry API

    /// @brief Attempts to send telemetry data with the given key and value of the given type.
    /// See https://thingsboard.io/docs/user-guide/telemetry/ for more information
    /// @tparam T Type of the passed value
    /// @param key Key of the key value pair we want to send
    /// @param value Value of the key value pair we want to send
    /// @return Whether sending the data was successful or not
    template<typename T>
    bool sendTelemetryData(char const * key, T const & value) {
        return sendKeyValue(key, value);
    }

    /// @brief Attempts to send aggregated telemetry data, expects iterators to a container containing Telemetry class instances.
    /// See https://thingsboard.io/docs/user-guide/telemetry/ for more information
    /// @tparam InputIterator Class that points to the begin and end iterator
    /// of the given data container, allows for using / passing either std::vector or std::array.
    /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator
    /// @param first Iterator pointing to the first element in the data container
    /// @param last Iterator pointing to the end of the data container (last element + 1)
    /// @return Whether sending the aggregated telemetry data was successful or not
#if THINGSBOARD_ENABLE_DYNAMIC
    template<typename InputIterator>
#else
    /// @tparam MaxKeyValuePairAmount Maximum amount of json key value pairs, which will ever be sent with this method to the cloud.
    /// Should simply be the biggest distance between first and last iterator this method is ever called with
    template<size_t MaxKeyValuePairAmount, typename InputIterator>
#endif // THINGSBOARD_ENABLE_DYNAMIC
    bool sendTelemetry(InputIterator const & first, InputIterator const & last) {
#if THINGSBOARD_ENABLE_DYNAMIC
        return sendDataArray(first, last, true);
#else
        return sendDataArray<MaxKeyValuePairAmount>(first, last, true);
#endif // THINGSBOARD_ENABLE_DYNAMIC
    }

    /// @brief Attempts to send custom json telemetry string.
    /// See https://thingsboard.io/docs/user-guide/telemetry/ for more information
    /// @param json String containing our json key value pairs we want to attempt to send
    /// @return Whether sending the data was successful or not
    bool sendTelemetryString(char const * json) {
        return Send_Json_String(TELEMETRY_TOPIC, json);
    }

    /// @brief Attempts to send telemetry key value pairs from custom source to the server.
    /// See https://thingsboard.io/docs/user-guide/telemetry/ for more information
    /// @param source JsonDocument containing our json key value pairs we want to send,
    /// is checked before usage for any possible occuring internal errors. See https://arduinojson.org/v6/api/jsondocument/ for more information
    /// @param json_size Size of the data inside the source
    /// @return Whether sending the data was successful or not
    bool sendTelemetryJson(JsonDocument const & source, size_t const & json_size) {
        return Send_Json(TELEMETRY_TOPIC, source, json_size);
    }

    //----------------------------------------------------------------------------
    // Attribute API

    /// @brief Attempts to send attribute data with the given key and value of the given type.
    /// See https://thingsboard.io/docs/user-guide/attributes/ for more information
    /// @tparam T Type of the passed value
    /// @param key Key of the key value pair we want to send
    /// @param value Value of the key value pair we want to send
    /// @return Whether sending the data was successful or not
    template<typename T>
    bool sendAttributeData(char const * key, T const & value) {
        return sendKeyValue(key, value, false);
    }

    /// @brief Attempts to send aggregated attribute data, expects iterators to a container containing Attribute class instances.
    /// See https://thingsboard.io/docs/user-guide/attributes/ for more information
    /// @tparam InputIterator Class that points to the begin and end iterator
    /// of the given data container, allows for using / passing either std::vector or std::array.
    /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator
    /// @param first Iterator pointing to the first element in the data container
    /// @param last Iterator pointing to the end of the data container (last element + 1)
    /// @return Whether sending the aggregated attribute data was successful or not
#if THINGSBOARD_ENABLE_DYNAMIC
    template<typename InputIterator>
#else
    /// @tparam MaxKeyValuePairAmount Maximum amount of json key value pairs, which will ever be sent with this method to the cloud.
    /// Should simply be the biggest distance between first and last iterator this method is ever called with
    template<size_t MaxKeyValuePairAmount, typename InputIterator>
#endif // THINGSBOARD_ENABLE_DYNAMIC
    bool sendAttributes(InputIterator const & first, InputIterator const & last) {
#if THINGSBOARD_ENABLE_DYNAMIC
        return sendDataArray(first, last, false);
#else
        return sendDataArray<MaxKeyValuePairAmount>(first, last, false);
#endif // THINGSBOARD_ENABLE_DYNAMIC
    }

    /// @brief Attempts to send custom json attribute string.
    /// See https://thingsboard.io/docs/user-guide/attributes/ for more information
    /// @param json String containing our json key value pairs we want to attempt to send
    /// @return Whether sending the data was successful or not
    bool sendAttributeString(char const * json) {
        return Send_Json_String(ATTRIBUTE_TOPIC, json);
    }

    /// @brief Attempts to send attribute key value pairs from custom source to the server.
    /// See https://thingsboard.io/docs/user-guide/attributes/ for more information
    /// @param source JsonDocument containing our json key value pairs we want to send,
    /// is checked before usage for any possible occuring internal errors. See https://arduinojson.org/v6/api/jsondocument/ for more information
    /// @param json_size Size of the data inside the source
    /// @return Whether sending the data was successful or not
    bool sendAttributeJson(JsonDocument const & source, size_t const & json_size) {
        return Send_Json(ATTRIBUTE_TOPIC, source, json_size);
    }

  private:
#if THINGSBOARD_ENABLE_STREAM_UTILS
    /// @brief Serialize the custom attribute source into the underlying client.
    /// Sends the given bytes to the client without requiring any temporary buffer at the cost of hugely increased send times
    /// @param topic Topic we want to send the data over
    /// @param source JsonDocument containing our json key value pairs we want to send,
    /// is checked before usage for any possible occuring internal errors. See https://arduinojson.org/v6/api/jsondocument/ for more information
    /// @param json_size Size of the data inside the source
    /// @return Whether sending the data was successful or not
    bool Serialize_Json(char const * topic, JsonDocument const & source, size_t const & json_size) {
        if (!m_client.begin_publish(topic, json_size)) {
            Logger::printfln(UNABLE_TO_SERIALIZE_JSON);
            return false;
        }
        BufferingPrint buffered_print(m_client, getBufferingSize());
        size_t const bytes_serialized = serializeJson(source, buffered_print);
        if (bytes_serialized < json_size) {
            Logger::printfln(UNABLE_TO_SERIALIZE_JSON);
            return false;
        }
        buffered_print.flush();
        return m_client.end_publish();
    }
#endif // THINGSBOARD_ENABLE_STREAM_UTILS

    /// @brief Returns the maximum amount of bytes that we want to allocate on the stack, before the memory is allocated on the heap instead
    /// @return Maximum amount of bytes we want to allocate on the stack
    size_t const & getMaximumStackSize() const {
        return m_max_stack;
    }

    /// @brief Returns the current receive buffer size of the underlying client interface
    /// @return Current internal send buffer size
    uint16_t getClientReceiveBufferSize() {
        return m_client.get_receive_buffer_size();
    }

    /// @brief Returns the current send buffer size of the underlying client interface
    /// @return Current internal receive buffer size
    uint16_t getClientSendBufferSize() {
        return m_client.get_send_buffer_size();
    }

    /// @brief Subscribes the given topic with the underlying client interface
    /// @param topic Topic that should be subscribed
    /// @return Whether subscribing was successfull or not
    bool clientSubscribe(char const * topic) {
        return m_client.subscribe(topic);
    }

    /// @brief Unsubscribes the given topic with the underlying client interface
    /// @param topic Topic that should be unsubscribed
    /// @return Whether unsubscribing was successfull or not
    bool clientUnsubscribe(char const * topic) {
        return m_client.unsubscribe(topic);
    }

    /// @brief Gets a mutable pointer to the request id, the current value is the id of the last sent request.
    /// Is used because each request to the cloud of the same type (attribute request, rpc request, over the air firmware update), has to use a different id to differentiate request and response.
    /// To ensure that we therefore simply provide a global request id that can be used and incremented by all request types
    /// @return Mutable reference to the request id
    size_t * getRequestID() {
        return &m_request_id;
    }

#if THINGSBOARD_ENABLE_STREAM_UTILS
    /// @brief Returns the amount of bytes that can be allocated to speed up fall back serialization with the StreamUtils class
    /// See https://github.com/bblanchon/ArduinoStreamUtils for more information on the underlying class used
    /// @return Amount of bytes allocated to speed up serialization
    size_t const & getBufferingSize() const {
      return m_buffering_size;
    }
#endif // THINGSBOARD_ENABLE_STREAM_UTILS

    /// @brief Connects to the previously set ThingsBoard server, as the given client with the given access token
    /// @param access_token Access token that connects this device with a created device on the ThingsBoard server,
    /// can be "provision", if the device creates itself instead
    /// @param client_id Client username that can be used to differentiate the user that is connecting the given device to ThingsBoard
    /// @param password Client password that can be used to authenticate the user that is connecting the given device to ThingsBoard
    /// @return Whether connecting to ThingsBoard was successful or not
    bool connectToHost(char const * access_token, char const * client_id, char const * password) {
        bool const connection_result = m_client.connect(client_id, access_token, password);
        if (!connection_result) {
            Logger::printfln(CONNECT_FAILED);
        }
        return connection_result;
    }

    /// @brief Resubscribes to topics that establish a permanent connection with MQTT, meaning they may receive more than one event over their lifetime,
    /// whereas other events that are only ever called once and then deleted after they have been handled are not resubscribed.
    /// Only the topics that establish a permanent connection are resubscribed, because all not yet received data is discard on the MQTT broker,
    // once we establish a connection again. This is the case because we connect with the cleanSession attribute set to true.
    // Therefore we can also clear the buffer of all non-permanent topics.
    void Resubscribe_Topics() {
        // Results are ignored, because the important part of clearing internal data structures always succeeds
        for (auto & api : m_api_implementations) {
            if (api == nullptr) {
                continue;
            }
            (void)api->Resubscribe_Topic();
        }
    }

    /// @brief Attempts to send a single key-value pair with the given key and value of the given type
    /// @tparam T Type of the passed value
    /// @param key Key of the key value pair we want to send
    /// @param value Value of the key value pair we want to send
    /// @param telemetry Whether the data we want to send should be sent as an attribute or telemetry data value
    /// @return Whether sending the data was successful or not
    template<typename T>
    bool sendKeyValue(char const * key, T const & value, bool telemetry = true) {
        const Telemetry t(key, value);
        if (t.IsEmpty()) {
            return false;
        }

        StaticJsonDocument<JSON_OBJECT_SIZE(1)> json_buffer;
        if (!t.SerializeKeyValue(json_buffer)) {
            Logger::printfln(UNABLE_TO_SERIALIZE);
            return false;
        }
        return telemetry ? sendTelemetryJson(json_buffer, Helper::Measure_Json(json_buffer)) : sendAttributeJson(json_buffer, Helper::Measure_Json(json_buffer));
    }

    /// @brief Attempts to send aggregated attribute or telemetry data
    /// @tparam InputIterator Class that points to the begin and end iterator
    /// of the given data container, allows for using / passing either std::vector or std::array.
    /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator
    /// @param first Iterator pointing to the first element in the data container
    /// @param last Iterator pointing to the end of the data container (last element + 1)
    /// @param telemetry Whether the data we want to send should be sent over the attribute or telemtry topic
    /// @return Whether sending the aggregated data was successful or not
#if THINGSBOARD_ENABLE_DYNAMIC
    template<typename InputIterator>
#else
    /// @tparam MaxKeyValuePairAmount Maximum amount of json key value pairs, which will ever be sent with this method to the cloud.
    /// Should simply be the biggest distance between first and last iterator this method is ever called with
    template<size_t MaxKeyValuePairAmount, typename InputIterator>
#endif // THINGSBOARD_ENABLE_DYNAMIC
    bool sendDataArray(InputIterator const & first, InputIterator const & last, bool telemetry) {
        size_t const size = Helper::distance(first, last);
#if THINGSBOARD_ENABLE_DYNAMIC
        // char const * are stored as only a pointer inside the JsonDocument --> zero copy, meaning the size for the strings is 0 bytes.
        // Data structure size, therefore only depends on the amount of key value pairs passed.
        // See https://arduinojson.org/v6/assistant/ for more information on the needed size for the JsonDocument
        TBJsonDocument json_buffer(JSON_OBJECT_SIZE(size));
#else
        if (size > MaxKeyValuePairAmount) {
            Logger::printfln(TOO_MANY_JSON_FIELDS, size, "MaxKeyValuePairAmount", MaxKeyValuePairAmount);
            return false;
        }
        StaticJsonDocument<JSON_OBJECT_SIZE(MaxKeyValuePairAmount)> json_buffer;
#endif // THINGSBOARD_ENABLE_DYNAMIC

#if THINGSBOARD_ENABLE_STL
        if (std::any_of(first, last, [&json_buffer](Telemetry const & data) { return !data.SerializeKeyValue(json_buffer); })) {
            Logger::printfln(UNABLE_TO_SERIALIZE);
            return false;
        }
#else
        for (auto it = first; it != last; ++it) {
            auto const & data = *it;
            if (!data.SerializeKeyValue(json_buffer)) {
                Logger::printfln(UNABLE_TO_SERIALIZE);
                return false;
            }
        }
#endif // THINGSBOARD_ENABLE_STL
        return telemetry ? sendTelemetryJson(json_buffer, Helper::Measure_Json(json_buffer)) : sendAttributeJson(json_buffer, Helper::Measure_Json(json_buffer));
    }

    /// @brief MQTT callback that will be called if a publish message is received from the server
    /// Payload contains data from the internal buffer of the MQTT client,
    /// therefore the buffer and the specific memory region the payload points too and the following length bytes need to live on for as long as this method has not finished.
    /// This could be a problem if the system uses FreeRTOS or another tasking system and the processing of the data is interrupted.
    /// Because if this happens and we then send data it is possible for the system to overwrite the memory region that contained the previous response.
    /// Therefore we simply assume that either the used MQTT client, has seperate input and output buffers
    /// or that the receiving of data is not executed on a seperate FreeRTOS tasks to other sends
    /// @param topic Previously subscribed topic, we got the response over
    /// @param payload Payload that was sent over the cloud and received over the given topic
    /// @param length Total length of the received payload
    void onMQTTMessage(char * topic, uint8_t * payload, unsigned int length) {
#if THINGSBOARD_ENABLE_DEBUG
        Logger::printfln(RECEIVE_MESSAGE, length, topic);
#endif // THINGSBOARD_ENABLE_DEBUG

#if THINGSBOARD_ENABLE_STL
#if THINGSBOARD_ENABLE_CXX20
        auto filtered_raw_api_implementations = m_api_implementations | std::views::filter([&topic](IAPI_Implementation const * api) {
#else
#if THINGSBOARD_ENABLE_DYNAMIC
        Vector<IAPI_Implementation *> filtered_raw_api_implementations = {};
#else
        Array<IAPI_Implementation *, MaxEndpointsAmount> filtered_raw_api_implementations = {};
#endif // THINGSBOARD_ENABLE_DYNAMIC
        std::copy_if(m_api_implementations.begin(), m_api_implementations.end(), std::back_inserter(filtered_raw_api_implementations), [&topic](IAPI_Implementation const * api) {
#endif // THINGSBOARD_ENABLE_CXX20
            return (api != nullptr && api->Get_Process_Type() == API_Process_Type::RAW && api->Compare_Response_Topic(topic));
        });

        for (auto & api : filtered_raw_api_implementations) {
            api->Process_Response(topic, payload, length);
        }

        // If the filtered api implementations was not emtpy it means the response was processed as its raw bytes representation atleast once,
        // and because we interpreted it as raw bytes instead of json, we skip the further processing of those raw bytes as json.
        // We do that because the received response is in that case not even valid json in the first place and would therefore simply fail deserialization
        if (!filtered_raw_api_implementations.empty()) {
            return;
        }
#else
        bool processed_response_as_raw = false;
        for (auto & api : m_api_implementations) {
            if (api == nullptr || api->Get_Process_Type() != API_Process_Type::RAW || !api->Compare_Response_Topic(topic)) {
                continue;
            }
            api->Process_Response(topic, payload, length);
            processed_response_as_raw = true;
        }

        if (processed_response_as_raw) {
            return;
        }
#endif // THINGSBOARD_ENABLE_STL

        // Calculate size with the total amount of commas, always denotes the end of a key-value pair besides for the last element in an array or in an object where the comma is not permitted,
        // therfore we have to add the space for another key-value pair for all the occurences of thoose symbols as well
        size_t const size = Helper::getOccurences(payload, ',', length) + Helper::getOccurences(payload, '{', length) + Helper::getOccurences(payload, '[', length);
#if THINGSBOARD_ENABLE_DYNAMIC
        // Buffer that we deserialize is writeable and not read only and therefore stored as a pointer inside the JsonDocument --> zero copy, meaning the size for the received payload is 0 bytes.
        // Data structure size, therefore only depends on the amount of key value pairs received.
        // See https://arduinojson.org/v6/assistant/ for more information on the needed size for the JsonDocument
        size_t const document_size = JSON_OBJECT_SIZE(size);
        if (m_max_response_size != 0U && document_size > m_max_response_size) {
            Logger::printfln(MAXIMUM_RESPONSE_EXCEEDED, document_size, m_max_response_size);
            return;
        }
        TBJsonDocument json_buffer(document_size);
        // Because we calcualte the allocation dynamically fromt he payload, which is user input, it could theoretically be malicious ({ "malicious" : "{{{{{{{{{..."}) and contain a lot of the symbols used to calculate the size.
        // But if that is the case adn the allocation still succeeds we delete the allocated memory relatively fast again so it shouldn't be a problem and if the allocation fails we simply return at this point with an appropriate error message
        if (json_buffer.capacity() != document_size) {
            Logger::printfln(HEAP_ALLOCATION_FAILED, document_size);
            return;
        }
#else
        if (size > MaxResponse) {
            Logger::printfln(TOO_MANY_JSON_FIELDS, size, "MaxResponse", MaxResponse);
            return;
        }
        size_t const document_size = JSON_OBJECT_SIZE(MaxResponse);
        StaticJsonDocument<document_size> json_buffer;
#endif // THINGSBOARD_ENABLE_DYNAMIC
#if THINGSBOARD_ENABLE_DEBUG
        Logger::printfln(ALLOCATING_JSON, document_size);
#endif // THINGSBOARD_ENABLE_DEBUG

        // The deserializeJson method we use, can use the zero copy mode because a writeable input was passed,
        // if that were not the case the needed allocated memory would drastically increase, because the keys would need to be copied as well.
        // See https://arduinojson.org/v6/doc/deserialization/ for more info on ArduinoJson deserialization
        DeserializationError const error = deserializeJson(json_buffer, payload, length);
        if (error) {
            Logger::printfln(UNABLE_TO_DE_SERIALIZE_JSON, error.c_str());
            return;
        }

#if THINGSBOARD_ENABLE_STL
#if THINGSBOARD_ENABLE_CXX20
        auto filtered_json_api_implementations = m_api_implementations | std::views::filter([&topic](IAPI_Implementation const * api) {
#else
#if THINGSBOARD_ENABLE_DYNAMIC
        Vector<IAPI_Implementation *> filtered_json_api_implementations = {};
#else
        Array<IAPI_Implementation *, MaxEndpointsAmount> filtered_json_api_implementations = {};
#endif // THINGSBOARD_ENABLE_DYNAMIC
        std::copy_if(m_api_implementations.begin(), m_api_implementations.end(), std::back_inserter(filtered_json_api_implementations), [&topic](IAPI_Implementation const * api) {
#endif // THINGSBOARD_ENABLE_CXX20
            return (api != nullptr && api->Get_Process_Type() == API_Process_Type::JSON && api->Compare_Response_Topic(topic));
        });

        for (auto & api : filtered_json_api_implementations) {
            api->Process_Json_Response(topic, json_buffer);
        }
#else
        for (auto & api : m_api_implementations) {
            if (api == nullptr || api->Get_Process_Type() != API_Process_Type::JSON || !api->Compare_Response_Topic(topic)) {
                continue;
            }
            api->Process_Json_Response(topic, json_buffer);
        }
#endif // THINGSBOARD_ENABLE_STL
    }

#if !THINGSBOARD_ENABLE_STL
    static void onStaticMQTTMessage(char * topic, uint8_t * payload, unsigned int length) {
        if (m_subscribedInstance == nullptr) {
            return;
        }
        m_subscribedInstance->onMQTTMessage(topic, payload, length);
    }

    static void staticMQTTConnect() {
        if (m_subscribedInstance == nullptr) {
            return;
        }
        m_subscribedInstance->Resubscribe_Topics();
    }

    static void staticSubscribeImplementation(IAPI_Implementation & api) {
        if (m_subscribedInstance == nullptr) {
            return;
        }
        m_subscribedInstance->Subscribe_API_Implementation(api);
    }

    static bool staticSendJson(char const * topic, JsonDocument const & source, size_t const & json_size) {
        if (m_subscribedInstance == nullptr) {
            return false;
        }
        return m_subscribedInstance->Send_Json(topic, source, json_size);
    }

    static bool staticSendJsonString(char const * topic, char const * json) {
        if (m_subscribedInstance == nullptr) {
            return false;
        }
        return m_subscribedInstance->Send_Json_String(topic, json);
    }

    static bool staticClientSubscribe(char const * topic) {
        if (m_subscribedInstance == nullptr) {
            return false;
        }
        return m_subscribedInstance->clientSubscribe(topic);
    }

    static bool staticClientUnsubscribe(char const * topic) {
        if (m_subscribedInstance == nullptr) {
            return false;
        }
        return m_subscribedInstance->clientUnsubscribe(topic);
    }

    static size_t * staticGetRequestID() {
        if (m_subscribedInstance == nullptr) {
            return nullptr;
        }
        return m_subscribedInstance->getRequestID();
    }

    static uint16_t staticGetClientReceiveBufferSize() {
        if (m_subscribedInstance == nullptr) {
            return 0U;
        }
        return m_subscribedInstance->getClientReceiveBufferSize();
    }

    static uint16_t staticGetClientSendBufferSize() {
        if (m_subscribedInstance == nullptr) {
            return 0U;
        }
        return m_subscribedInstance->getClientSendBufferSize();
    }

    static bool staticSetBufferSize(uint16_t receive_buffer_size, uint16_t send_buffer_size) {
        if (m_subscribedInstance == nullptr) {
            return false;
        }
        return m_subscribedInstance->setBufferSize(receive_buffer_size, send_buffer_size);
    }

    // PubSub client cannot call a instanced method when message arrives on subscribed topic.
    // Only free-standing function is allowed.
    // To be able to forward event to an instance, rather than to a function, this pointer exists.
    static ThingsBoardSized *m_subscribedInstance;
#endif // !THINGSBOARD_ENABLE_STL

    IMQTT_Client&                                   m_client = {};              // MQTT client instance.
    size_t                                          m_max_stack = {};           // Maximum stack size we allocate at once.
    size_t                                          m_request_id = {};          // Internal id used to differentiate which request should receive which response for certain API calls. Can send 4'294'967'296 requests before wrapping back to 0
#if THINGSBOARD_ENABLE_STREAM_UTILS
    size_t                                          m_buffering_size = {};      // Buffering size used to serialize directly into client.
#endif // THINGSBOARD_ENABLE_STREAM_UTILS
#if !THINGSBOARD_ENABLE_DYNAMIC
    Array<IAPI_Implementation*, MaxEndpointsAmount> m_api_implementations = {}; // Can hold a pointer to all possible API implementations (Server side RPC, Client side RPC, Shared attribute update, Client-side or shared attribute request, Provision)   
#else
    size_t                                          m_max_response_size = {};   // Maximum size allocated on the heap to hold the Json data structure for received cloud response payload, prevents possible malicious payload allocaitng a lot of memory
    Vector<IAPI_Implementation*>                    m_api_implementations = {}; // Can hold a pointer to all  possible API implementations (Server side RPC, Client side RPC, Shared attribute update, Client-side or shared attribute request, Provision)   
#endif // !THINGSBOARD_ENABLE_DYNAMIC                
};

#if !THINGSBOARD_ENABLE_STL
#if !THINGSBOARD_ENABLE_DYNAMIC
template<size_t MaxResponse, size_t MaxEndpointsAmount, typename Logger>
ThingsBoardSized<MaxResponse, MaxEndpointsAmount, Logger> *ThingsBoardSized<MaxResponse, MaxEndpointsAmount, Logger>::m_subscribedInstance = nullptr;
#else
template<typename Logger>
ThingsBoardSized<Logger> *ThingsBoardSized<Logger>::m_subscribedInstance = nullptr;
#endif // !THINGSBOARD_ENABLE_DYNAMIC
#endif // !THINGSBOARD_ENABLE_STL

using ThingsBoard = ThingsBoardSized<>;

#endif // ThingsBoard_h
